package cn.tedu.tmall.admin.mall.service.impl;

import cn.tedu.tmall.admin.mall.dao.repository.IMallCategoryCacheRepository;
import cn.tedu.tmall.admin.mall.dao.repository.IMallCategoryRepository;
import cn.tedu.tmall.admin.mall.pojo.param.MallCategoryAddParam;
import cn.tedu.tmall.admin.mall.pojo.po.MallCategoryPO;
import cn.tedu.tmall.admin.mall.service.IMallCategoryService;
import cn.tedu.tmall.common.ex.ServiceException;
import cn.tedu.tmall.common.pojo.po.MallCategoryCachePO;
import cn.tedu.tmall.common.pojo.vo.MallCategoryTreeVO;
import cn.tedu.tmall.common.web.ServiceCodeEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Slf4j
@Service
public class MallCategoryService implements IMallCategoryService {

    @Autowired
    private IMallCategoryRepository mallCategoryRepository;

    @Autowired
    private IMallCategoryCacheRepository mallCategoryCacheRepository;

    /**
     * 简单本地缓存  https://blog.csdn.net/zzzili/article/details/131326504
     */
    private Map<String, List<MallCategoryTreeVO>> cached = new HashMap<>();

    /**
     * 增加类别
     * 1 名称不能重复 重复报错
     * 2 类别的级别是 子级还是顶级
     * 顶级的 parent_id == 0  子级 parent_id不为0
     * 3 如果是子级 需要判断父级是否存在 太原要判断山西是否存在
     * 4 如果是父级 直接保存  山西直接保存
     * 5 判断父级是否禁用  可选
     * 6 计算深度
     * 父级 parentId=0  深度 = 1
     * 子级   深度 = 父级的深度 + 1
     * 7 计算isParent
     * 如果是父级 isParent = 0  山西 = 0
     * 如果是子级 把父级的isParent = 1  太原的父级山西  isParent = 1
     *
     * @param param
     */
    @Override
    public void addCategory(MallCategoryAddParam param) {
        //1 名称不能重复 重复报错
        //"".substring(100); 故意抛出异常,体会全局捕获
        String name = param.getName();
        Integer count = mallCategoryRepository.countCategoryByName(name);
        MallCategoryPO mallCategoryPO = null;
        if (count != 0) {
            //存在
            //throw new ServiceException(1000,"名称不能重复"); 魔数
            //throw new ServiceException(ServiceCode.OK,"名称不能重复");
            //throw new ServiceException(ServiceCodeEnum.OK.getCode(),ServiceCodeEnum.BAD_REQUEST.getMsg());
            //throw new ServiceException(ServiceCodeEnum.BAD_REQUEST,"名称不能重复");
            throw new ServiceException(ServiceCodeEnum.BAD_REQUEST_NAME_EXIST);
        }
        Long parentId = param.getParentId();
        Integer depth = 1;
        if (parentId == 0) {
            //顶级 直接保存
            mallCategoryPO = new MallCategoryPO();
            BeanUtils.copyProperties(param, mallCategoryPO);
            mallCategoryPO.setDepth(depth);
            mallCategoryPO.setIsParent(0);
            mallCategoryRepository.save(mallCategoryPO);
        } else {
            //子级
            //查询父级信息
            MallCategoryPO parentCategoryPO = mallCategoryRepository.getCategoryById(parentId);
            if (parentCategoryPO != null) {
                //判断是否启用 先不实现
                //计算深度
                //子级深度 = 父级的深度 + 1
                depth = parentCategoryPO.getDepth() + 1;
                //先保存子级 还是先更新他父级的isParent = 1
                mallCategoryPO = new MallCategoryPO();
                BeanUtils.copyProperties(param, mallCategoryPO);
                mallCategoryPO.setDepth(depth);
                mallCategoryPO.setIsParent(0);
                //mallCategoryPO.setGmtCreate();
                //mallCategoryPO.setGmtModified(); 每一个表都有这两个字段,都需要设置
                Integer rows = mallCategoryRepository.save(mallCategoryPO);
                if (rows != 0) {
                    //更新父级的isParent = 1
                    if (parentCategoryPO.getIsParent() == 0) {
                        MallCategoryPO update = new MallCategoryPO();
                        update.setId(parentId);
                        update.setIsParent(1);
                        mallCategoryRepository.updateCategoryById(update);
                    }
                }
            } else {
                throw new RuntimeException("父级不存在");
            }
        }
        //更新完数据库以后,我要同时更新redis
        // key ALL value vos
        // 1 全部取出来,然后在set 回去
        List<MallCategoryTreeVO> mallCategoryTreeVOS = treeCategory();
        mallCategoryCacheRepository.saveTreeCategory(mallCategoryTreeVOS);

        // 2 有没有办法只更新部分数据  过去的K-V(vos) 这种数据类型 不支持 考虑新的数据类型?
        // 增量更新  上次的数据基础上继续更新
        // key xxxx+parentId value  mallCategoryPO
        MallCategoryCachePO categoryCachePO = new MallCategoryCachePO();
        BeanUtils.copyProperties(mallCategoryPO, categoryCachePO);
        mallCategoryCacheRepository.saveCategoryByParentId(mallCategoryPO.getParentId(), categoryCachePO);
    }

    /**
     * 把平铺的数据表结构变成多层级的树形结构
     *
     * @return
     */
    @Override
    public List<MallCategoryTreeVO> treeCategory() {
        //从数据库查询数据
        //A数据量较小 几百条  可以获取所有
        //B数据量较大 几万条 能不能获取所有呢?
        // 1 数据库能不能查出来 性能很差
        // 2 内存能不能接的住  如果接不住 OutMemoryError 内存溢出
        // todo 数据太大怎么解决
        // 先查一部分 哪部分?
        //A 全查
        //  String key = "ALL";
        //  List<MallCategoryTreeVO> cachedVo = cached.get("ALL");
     /*   if (!CollectionUtils.isEmpty(cachedVo)) {
            return cachedVo;
        }else {*/
        List<MallCategoryPO> all =
                mallCategoryRepository.getAll();
        log.debug("查询所有类别数据:{}", all);
        //把po转换为vo
        // 怎么把平铺的数据转换为层级的数据 todo
        //1 先取最顶级 parent = 0  北京  山西
        //2 取下一级  parent_id = 北京的id 山西的id  北京的下一级 海淀 山西的下一级是 太原
        //3 取下一级的下一级  parent_id = 海淀id parent_id = 太原id  海淀的下一级 没有终止 太原的下一级
        //4 .... 何时是个头呢,什么时候停止呢  1 判断当前级别还有没有子级 如果没了,stop
        //5 递归  自己调自己  终止条件 退出这个循环  -Xss jvm 栈 栈溢出
        //6 如果是明确的知道层级 层级很少 省市区 可以 简单的方法 一级一级的往下找

        //先取最顶级
        List<MallCategoryTreeVO> topCategory = getTopCategory(all);
        //获取顶级的子级
        //parent_id 等于顶级的id 就是顶级的子级
        for (MallCategoryTreeVO topVo : topCategory) {
            appendChild(topVo, all);
        }
        //cached.put(key,topCategory);
        return topCategory;
        // }
        //return mockData();
    }

    @Override
    public List<MallCategoryTreeVO> treeTopCategory() {
        //取最顶级
        List<MallCategoryPO> top = mallCategoryRepository.getTopCategory();
        log.debug("查询所有顶级类别数据:{}", top);
        return categoryPOS2VO2(top);
    }

    @Override
    public List<MallCategoryTreeVO> treeChildrenCategory(Long parentId) {
        List<MallCategoryPO> children = mallCategoryRepository.getChildrenCategory(parentId);
        return categoryPOS2VO2(children);
    }

    /**
     * 把PO列表转化为VO列表
     *
     * @param pos
     * @return
     */
    private List<MallCategoryTreeVO> categoryPOS2VO2(List<MallCategoryPO> pos) {
        List<MallCategoryTreeVO> result = new ArrayList<>();
        pos.forEach(po -> {
            //转换为vo
            result.add(categoryPO2VO(po));
        });
        return result;
    }

    /**
     * 追加子级
     * 递归 1  自己调自己  2 终止条件 退出这个循环
     * 入参1 是给谁设置子级
     * 出参 是入参1的子级
     */
    private MallCategoryTreeVO appendChild(MallCategoryTreeVO vo, List<MallCategoryPO> all) {
        //这个第一个入参Vo设置子级
        List<MallCategoryTreeVO> children = new ArrayList<>();
        for (MallCategoryPO mallCategoryPO : all) {
            //这两个家伙直接比较 用== 还是equals
            if (vo.getId().equals(mallCategoryPO.getParentId())) {
                MallCategoryTreeVO childVO = categoryPO2VO(mallCategoryPO);
                children.add(appendChild(childVO, all)); //去找childVo的子级
            } else {
                //没有子级了,终止条件 不调用appendChild,退出了
            }
        }
        vo.setChildren(children);
        return vo;
        //退出条件是没有子级 isParent = 0
        //if(//没找到子级 ) { //终止条件
        //退出
        //}else {
        //增加子级
        //子级获取到 增加的 children 列表里
        //MallCategoryTreeVO child = new MallCategoryTreeVO();
        //appendChild(child); //继续找子级

        //children.add(child); 导致没法继续找子级

        //}
        //return null;
    }

    //public static void main(String[] args) { //1行
    //   main(args); //2
    //调用逻辑  1 2 1 2 1 2
    //}

    /**
     * 从所有的类别中获取顶级类别
     *
     * @param mallCategoryPOS
     * @return
     */
    private List<MallCategoryTreeVO> getTopCategory(List<MallCategoryPO> mallCategoryPOS) {
        List<MallCategoryTreeVO> result = new ArrayList<>();
        mallCategoryPOS.forEach(po -> {
            if (po.getParentId() == 0) {
                //取最顶级 并且 转换为vo
                result.add(categoryPO2VO(po));
            }
        });

        return result;
    }

    /**
     * PO转换为VO
     *
     * @param po
     * @return
     */
    private MallCategoryTreeVO categoryPO2VO(MallCategoryPO po) {
        MallCategoryTreeVO vo = new MallCategoryTreeVO();
        BeanUtils.copyProperties(po, vo);
        //vo.setName(po.getName());
        return vo;
    }

    private List<MallCategoryTreeVO> mockData() {
        //mock  准备的假数据
        MallCategoryTreeVO bj = new MallCategoryTreeVO();
        bj.setId(1L);
        bj.setName("北京");

        MallCategoryTreeVO hd = new MallCategoryTreeVO();
        hd.setId(2L);
        hd.setName("海淀");

        List<MallCategoryTreeVO> bjchildren = new ArrayList<>();
        bjchildren.add(hd);
        bj.setChildren(bjchildren);


        MallCategoryTreeVO sx = new MallCategoryTreeVO();
        sx.setId(2L);
        sx.setName("山西");

        MallCategoryTreeVO ty = new MallCategoryTreeVO();
        ty.setId(3L);
        ty.setName("太原");

        List<MallCategoryTreeVO> sxchildren = new ArrayList<>();

        sxchildren.add(ty);
        sx.setChildren(sxchildren);


        MallCategoryTreeVO yz = new MallCategoryTreeVO();
        yz.setId(4L);
        yz.setName("迎泽");
        List<MallCategoryTreeVO> tychildren = new ArrayList<>();
        tychildren.add(yz);
        ty.setChildren(tychildren);


        List result = new ArrayList();
        result.add(bj);
        result.add(sx);

        return result;
    }

    public static void main(String[] args) {
        Integer a = 10000;
        Integer b = 10000;
        System.out.println(a == b);

        Integer c = 200;
        Integer d = 200;
        System.out.println(c == d);

        Integer e = 100;
        Integer f = 100;
        System.out.println(e == f);

    }
}
